package eip.chapter6.messagefilter.mediator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.log4j.Logger;
import org.apache.synapse.MessageContext;
import org.apache.synapse.Mediator;
import org.jaxen.JaxenException;

/**
 * This class serves as a filter to detect duplicate messages. The difficulty 
 * with this class is that Synapse creates mediators anew for each request. So
 * we need to create a store where we can keep the received messages. For this
 * example we do this in a static field.
 * 
 * @author nl24167
 */
public class DoubleDeclarationMediator implements Mediator {

	/** the static map, shared between all the instances */
	private static Map<String,Map<String,List<String>>> receivedMessages = new HashMap<String,Map<String,List<String>>>();
	
	private static Logger LOG = Logger.getLogger(DoubleDeclarationMediator.class);
	
	/**
	 * This method checks whether we've received messages for this specific
	 * hotel and with this id. The key for the store is the address to where
	 * the message is send to (its endpoint).
	 */
	public boolean mediate(MessageContext synCtx) {
		boolean result = true;
		
		try {
			OMElement declarationMsg = synCtx.getEnvelope().getBody();
			
			String endpoint = synCtx.getTo().getAddress(); 
			String hotelId = getValue("tns:declaration-message/tns:hotel/@id",declarationMsg);
			String msgId = getValue("tns:declaration-message/tns:declaration/@id",declarationMsg);
			
			result = updateMap(endpoint,hotelId,msgId);
		} catch (Exception e) {
			LOG.error("Error occured in Double mediator, ignoring message.",e);
			result = false;
		}
		
		return result;
	}

	/**
	 * Returns the type of mediator this is
	 */
	public String getType() {
		return "DoubleDeclarationMediator";
	}
	
	/**
	 * Return the result of an xpath expression
	 * 
	 * @param xpath
	 * @param node
	 * @return
	 * @throws JaxenException
	 */
	private String getValue(String xpath, OMElement node) throws JaxenException {
		AXIOMXPath path = new AXIOMXPath(xpath);
		path.addNamespace("tns","http://www.sleepingbeauty.com/declarations");
		Object result = path.selectSingleNode(node);
		if (result instanceof OMAttribute) {
			return ((OMAttribute)result).getAttributeValue();
		} else if (result instanceof OMElement) {
			return ((OMElement)result).getText();
		} else {
			return null;	
		}
	}
	
	/**
	 * Helper method that checks the cache to see if we've already processed the message for
	 * a specific hotel with a specific id.
	 * 
	 * @param endpoint the endpoint we're checking for
	 * @param hotel the hotel that send the declaration
	 * @param msgId the id of the declaration
	 * @return true if the declaration was not found, false if it was found
	 */
	private synchronized boolean updateMap(String endpoint, String hotel, String msgId) {
		boolean result = false;
		Map<String,List<String>> receivedMap = receivedMessages.get(endpoint);
		if (receivedMap == null) {
			// we need to create a new Map for this entrypoint
			receivedMap = new HashMap<String,List<String>>();
			ArrayList<String> list = new ArrayList<String>();
			list.add(msgId);
			// store this map and the list in the static messages variable
			receivedMap.put(hotel,list);
			receivedMessages.put(endpoint,receivedMap);
			result = true;
		} else {
			// we have a map for this entrypoint, now we need to check if the map contains
			// our hotel,msgId combination
			List<String> list = receivedMap.get(hotel);
			if (list==null) {
				list = new ArrayList<String>();
				list.add(msgId);
				receivedMap.put(hotel,list);
				result = true;
			} else {
				if (list.contains(msgId)) {
					// the list already contains the id, so we can skip this message
					LOG.info("Skipping received message for: " + endpoint + " for hotel: " + hotel + " with id " + msgId);
					result = false;
				} else {
					list.add(msgId);
					result = true;
				}
			}
		}
		return result;
	}
}
